// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <turbo/times/time.h>
#include <kthread/internal/mutex.h>

__BEGIN_DECLS
extern int kthread_cond_init(kthread_cond_t *__restrict cond,
                             const kthread_condattr_t *__restrict cond_attr);
extern int kthread_cond_destroy(kthread_cond_t *cond);
extern int kthread_cond_signal(kthread_cond_t *cond);
extern int kthread_cond_broadcast(kthread_cond_t *cond);
extern int kthread_cond_wait(kthread_cond_t *__restrict cond,
                             kthread_mutex_t *__restrict mutex);
extern int kthread_cond_timedwait(
        kthread_cond_t *__restrict cond,
        kthread_mutex_t *__restrict mutex,
        const struct timespec *__restrict abstime);
__END_DECLS

namespace kthread {

    class ConditionVariable {
        TURBO_DISALLOW_COPY_AND_ASSIGN(ConditionVariable);

    public:
        typedef kthread_cond_t *native_handler_type;

        ConditionVariable() {
            KCHECK_EQ(0, kthread_cond_init(&_cond, nullptr));
        }

        ~ConditionVariable() {
            KCHECK_EQ(0, kthread_cond_destroy(&_cond));
        }

        native_handler_type native_handler() { return &_cond; }

        void wait(std::unique_lock<kthread::Mutex> &lock) {
            kthread_cond_wait(&_cond, lock.mutex()->native_handler());
        }

        void wait(std::unique_lock<kthread_mutex_t> &lock) {
            kthread_cond_wait(&_cond, lock.mutex());
        }

        // Unlike std::condition_variable, we return ETIMEDOUT when time expires
        // rather than std::timeout
        int wait_for(std::unique_lock<kthread::Mutex> &lock,
                     long timeout_us) {
            return wait_until(lock, turbo::Time::to_timespec(
                    turbo::Time::future_time(turbo::Duration::microseconds(timeout_us))));
        }

        int wait_for(std::unique_lock<kthread_mutex_t> &lock,
                     long timeout_us) {
            return wait_until(lock, turbo::Time::to_timespec(
                    turbo::Time::future_time(turbo::Duration::microseconds(timeout_us))));
        }

        int wait_until(std::unique_lock<kthread::Mutex> &lock,
                       timespec duetime) {
            const int rc = kthread_cond_timedwait(
                    &_cond, lock.mutex()->native_handler(), &duetime);
            return rc == ETIMEDOUT ? ETIMEDOUT : 0;
        }

        int wait_until(std::unique_lock<kthread_mutex_t> &lock,
                       timespec duetime) {
            const int rc = kthread_cond_timedwait(
                    &_cond, lock.mutex(), &duetime);
            return rc == ETIMEDOUT ? ETIMEDOUT : 0;
        }

        void notify_one() {
            kthread_cond_signal(&_cond);
        }

        void notify_all() {
            kthread_cond_broadcast(&_cond);
        }

    private:
        kthread_cond_t _cond;
    };

}  // namespace kthread
